package com.example.discover.viewmodel

import android.util.Log
import androidx.lifecycle.*
import com.example.featuresdk.model.Book
import com.example.discover.repository.BookRepository
import com.example.featuresdk.vo.Status
import com.example.featuresdk.vo.UiState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject

/*The ViewModel should create coroutines
 * https://developer.android.google.cn/kotlin/coroutines/coroutines-best-practices#viewmodel-coroutines
 *
 * ViewModel classes should prefer creating coroutines instead of exposing suspend functions to perform business logic.
 * Suspend functions in the ViewModel can be useful if instead of exposing state using a stream of data,
 * only a single value needs to be emitted.
 */
@HiltViewModel
class BooksViewModel @Inject constructor(private val repository: BookRepository): ViewModel() {

    /* Don't expose mutable types
     * https://developer.android.google.cn/kotlin/coroutines/coroutines-best-practices#mutable-types
     *
     * Prefer exposing immutable types to other classes.
     * In this way, all changes to the mutable type is centralized in one class making it easier
     * to debug when something goes wrong.
     */
    private val _uiState = MutableStateFlow(UiState<List<Book>>())
    val uiState: StateFlow<UiState<List<Book>>> = _uiState

    val commands = object: BooksUiAction(){
        override fun getAll() = launch {
            _uiState.value.data = repository.getAll()
            _uiState.value.status = Status.Success
        }

        override fun get(isbn: String) = launch {
            //repository.get(isbn)
            TODO("Not implemented yet")
        }

        override fun create(book: Book) = launch {
            repository.insert(*arrayOf(book))
        }

        override fun update(book: Book) = launch {
            Log.i("BooksUiAction","update book = ${book.toString()}")
            repository.update(book)
        }

        override fun delete(isbn: String) = launch {
            repository.delete(isbn)
        }
    }

    /* Creating coroutines in the business and data layer
     * (https://developer.android.google.cn/kotlin/coroutines/coroutines-best-practices#create-coroutines-data-layer)
     * If the work to be done in those coroutines are relevant only when the user is present on the current screen,
     * it should follow the caller's lifecycle.
     *
     * viewModelScope.launch{ }
     * Launching a new coroutine to perform UI action for data operations in a non-blocking way
     *
     * viewModelScope is a CoroutineScope tied to this ViewModel.
     * This scope will be canceled when ViewModel will be cleared, i.e ViewModel.onCleared is called.
     * This scope is bound to Dispatchers.Main.immediate
     *
     * viewModelScope launches a new coroutine without blocking the current thread
     * and returns a reference to the coroutine as a Job.
     * The coroutine is cancelled when the resulting job is cancelled.
     *
     * Exception handling
     * (https://developer.android.google.cn/kotlin/coroutines/coroutines-best-practices#exceptions)
     * Improperly handled exceptions thrown in coroutines can make your app crash.
     * If exceptions are likely to happen, catch them in the body of any coroutines created with viewModelScope or lifecycleScope.
     */
    private fun launch(uiActionCommand: suspend ()->Unit){
        viewModelScope.launch {
            try {
                uiActionCommand()
            }catch (t: Throwable){
                _uiState.value.status = Status.Error
                _uiState.value.message = t.message
            }
        }
    }

    init {
        Log.i("BooksViewModel","***BooksViewModel instance is created***")
        commands.getAll()
    }

    override fun onCleared() {
        Log.i("BooksViewModel","onCleared() ${this.toString()}")
    }
}